package com.oriole.admin.config;

import com.oriole.admin.utils.JacksonUtil;
import com.oriole.common.constant.Constants;
import com.oriole.common.util.RsaFileUtils;
import com.oriole.common.util.RsaUtils;
import com.oriole.entity.security.OrioleUserDetails;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultHeader;
import io.jsonwebtoken.impl.DefaultJwsHeader;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.lang.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import java.io.Serializable;
import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 *
 * @author doublelife
 * Email: hautxxxyzjk@163.com
 * Date: 2019-03-28 21:25
 * Description: token工具类
 */
public class TokenProvider implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final Logger log = LoggerFactory.getLogger(TokenProvider.class);

    private static CompressionCodecResolver codecResolver = new DefaultCompressionCodecResolver();

    public Claims getClaims(String token) {
        String publicKey = RsaFileUtils.getContent("public.txt", " PUBLIC KEY");
        RSAPublicKey rsaPublicKey = RsaUtils.getPublicKey(publicKey);
        try {
            return Jwts.parser().setSigningKey(rsaPublicKey).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
            log.debug("JwtException");
            return null;
        }
    }

    public Boolean isExpired(String token) {
        try {
            return getClaims(token).getExpiration().before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

    public Object getByTokenKey(String token, String key) {
        Claims claims = getClaims(token);
        return claims.get(key);
    }

    public String createToken(Authentication authentication) {
        Map<String, Object> claims = new HashMap<>(8);
        Object principal = authentication.getPrincipal();
        if (principal instanceof OrioleUserDetails) {
            OrioleUserDetails userDetails = (OrioleUserDetails) principal;
            claims.put(Constants.JWT_PRINCIPAL, userDetails.getUsername());
            claims.put(Constants.USER_ID, userDetails.getId());
        }
        Object credentials = authentication.getCredentials();
        if (credentials != null) {
            claims.put(Constants.JWT_CREDENTIALS, credentials);
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        if (!authorities.isEmpty()) {
            claims.put(Constants.JWT_AUTHORITIES, authorities);
        }
        return createToken(claims);
    }

    public String createToken(Map<String, Object> claims) {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(Constants.JKS_KEYSTORE), Constants.JKS_KEYPASS.toCharArray());
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(Constants.JKS_ALIAS);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + 12 * 60 * 60 * 1000L))
                .signWith(SignatureAlgorithm.RS256, keyPair.getPrivate())
                .compact();
    }

    public Map<String, Object> parseJwtPayload(String jwt) {
        Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
        String base64UrlEncodedHeader = null;
        String base64UrlEncodedPayload = null;
        String base64UrlEncodedDigest = null;
        int delimiterCount = 0;
        StringBuilder sb = new StringBuilder(128);
        for (char c : jwt.toCharArray()) {
            if (c == '.') {
                CharSequence tokenSeq = io.jsonwebtoken.lang.Strings.clean(sb);
                String token = tokenSeq != null ? tokenSeq.toString() : null;

                if (delimiterCount == 0) {
                    base64UrlEncodedHeader = token;
                } else if (delimiterCount == 1) {
                    base64UrlEncodedPayload = token;
                }
                delimiterCount++;
                sb.setLength(0);
            } else {
                sb.append(c);
            }
        }
        if (delimiterCount != 2) {
            String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
            throw new MalformedJwtException(msg);
        }
        if (sb.length() > 0) {
            base64UrlEncodedDigest = sb.toString();
        }
        if (base64UrlEncodedPayload == null) {
            throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
        }
        Header header;
        CompressionCodec compressionCodec = null;
        if (base64UrlEncodedHeader != null) {
            String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
            Map<String, Object> m = JacksonUtil.json2Map(origValue);
            if (base64UrlEncodedDigest != null) {
                header = new DefaultJwsHeader(m);
            } else {
                header = new DefaultHeader(m);
            }
            compressionCodec = codecResolver.resolveCompressionCodec(header);
        }
        String payload;
        if (compressionCodec != null) {
            byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
            payload = new String(decompressed, io.jsonwebtoken.lang.Strings.UTF_8);
        } else {
            payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
        }
        return JacksonUtil.json2Map(payload);
    }

}
